using System;
using Extensions.FPMath;
using Game.Config;
using Unity.Entities;
using Unity.Mathematics;

namespace Game.Core
{
    public unsafe static class PropertyUtil
    {
        private static float StrengthToMinMeleeAttack = 0.95f;
        private static float StrengthToMaxMeleeAttack = 1.05f;
        private static float StrengthToCritRatio = 0.01f;
        private static float StrengthToEncumbered = 1f;

        private static float DexterityToMinRangeAttack = 1.01f;
        private static float DexterityToMaxRangeAttack = 0.99f;
        private static float DexterityToAttackSpeed = 0.1f;
        private static float DexterityToDodge = 1f;

        private static float ConstitutionToHealthPoint = 25.0f;
        private static float ConstitutionToHealthRecover = 0.05f;
        private static float ConstitutionToArmor = 1f;
        private static float ConstitutionToResilience = 0.01f;

        private static float IntellectualToAbilityPower = 0.01f;
        private static float IntellectualToAbilityPoint = 10f;
        private static float IntellectualToAbilityRecover = 1f;

        private static float LuckyToCritRate = 0.01f;
        private static float LuckyToBlock = 1f;
        private static float LuckyToHit = 1f;

        private static float CharismaToResistance = 1f;
        private static float CharismaToPenetration = 1f;
        private static float CharismaToTroops = 1f;

        private static float AttackSpeedToInterval = 0.001f; 


        public static void InitPropertyUtil(PropertyConfig config)
        {
            ConstitutionToHealthPoint = config.ConstitutionToHealthPoint;
            ConstitutionToHealthRecover = config.ConstitutionToHealthRecover;
        }

        #region Calc Property

        public static PropertyInfo CalcPropertyData(int id, int level = 1)
        {
            var baseInfo = PropertyConfig.GetPropertyData(id).ToPropertyInfo();


            return baseInfo;
        }

        private static float CalcStrength(this in PropertyData data) => data.Base.Strength + data.Advanced.Strength;
        private static float CalcDexterity(this in PropertyData data) => data.Base.Dexterity + data.Advanced.Dexterity;
        private static float CalcConstitution(this in PropertyData data) => data.Base.Constitution + data.Advanced.Constitution;
        private static float CalcIntellectual(this in PropertyData data) => data.Base.Intellectual + data.Advanced.Intellectual;
        private static float CalcLucky(this in PropertyData data) => data.Base.Lucky + data.Advanced.Lucky;
        private static float CalcCharisma(this in PropertyData data) => data.Base.Charisma + data.Advanced.Charisma;

        private static float CalcMinMeleeAttackWithFormula(this in PropertyData data) =>
            data.Base.MinMeleeAttack + data.CalcStrength() * StrengthToMinMeleeAttack;


        private static float CalcMaxMeleeAttackWithFormula(this in PropertyData data) =>
            data.Base.MaxMeleeAttack + data.CalcStrength() * StrengthToMaxMeleeAttack;


        private static float CalcMinRangeAttackWithFormula(this in PropertyData data) =>
            data.Base.MinRangeAttack + data.CalcDexterity() * DexterityToMinRangeAttack;
        

        private static float CalcMaxRangeAttackWithFormula(this in PropertyData data) =>
            data.Base.MaxRangeAttack + data.CalcDexterity() * DexterityToMaxRangeAttack;
        
        private static float CalcAttackSpeedWithFormula(this in PropertyData data) =>
            data.Base.AttackSpeed + data.CalcDexterity() * DexterityToAttackSpeed;
        
        private static float CalcAttackIntervalWithFormula(this in PropertyData data) =>
            math.max(data.Base.AttackInterval - data.CalcAttackSpeedWithFormula() * AttackSpeedToInterval, 0f);
        
        private static float CalcHitWithFormula(this in PropertyData data) =>
            data.Base.Hit + data.CalcLucky() * LuckyToHit;
        
        private static float CalcCritRateWithFormula(this in PropertyData data) =>
            data.Base.CritRate + data.CalcLucky() * LuckyToCritRate;

        private static float CalcCritRatioWithFormula(this in PropertyData data) =>
            data.Base.CritRatio + data.CalcStrength() * StrengthToCritRatio;
        
        private static float CalcAbilityPowerWithFormula(this in PropertyData data) =>
            data.Base.AbilityPower + data.CalcIntellectual() * IntellectualToAbilityPower;
        
        private static float CalcPenetrationWithFormula(this in PropertyData data) =>
            data.Base.Penetration + data.CalcCharisma() * CharismaToPenetration;
        
        private static float CalcAbilityPointWithFormula(this in PropertyData data) =>
            data.Base.MaxAbilityPoint + data.CalcIntellectual() * IntellectualToAbilityPoint;
        
        private static float CalcAbilityRecoverWithFormula(this in PropertyData data) =>
            data.Base.AbilityRecover + data.CalcIntellectual() * IntellectualToAbilityRecover;
        
        private static float CalcExtraDamageWithFormula(this in PropertyData data) => data.Base.ExtraDamage;
        
        private static float CalcMaxHealthPointWithFormula(this in PropertyData data) =>
            data.Base.MaxHealthPoint + data.CalcConstitution() * ConstitutionToHealthPoint;
        
        private static float CalcHealthRecoverWithFormula(this in PropertyData data) =>
            data.Base.HealthRecover + data.CalcConstitution() * ConstitutionToHealthRecover;
        
        private static float CalcArmorWithFormula(this in PropertyData data) =>
            data.Base.Armor + data.CalcConstitution() * ConstitutionToArmor;
        
        private static float CalcBlockWithFormula(this in PropertyData data) =>
            data.Base.Block + data.CalcLucky() * LuckyToBlock;
        
        private static float CalcDodgeWithFormula(this in PropertyData data) =>
            data.Base.Dodge + data.CalcDexterity() * DexterityToDodge;

        private static float CalcResilienceWithFormula(this in PropertyData data) =>
            data.Base.Resilience + data.CalcConstitution() * ConstitutionToResilience;
        
        private static float CalcResistanceWithFormula(this in PropertyData data) =>
            data.Base.Resistance + data.CalcCharisma() * CharismaToResistance;
        
        private static float CalcMaxEncumberedWithFormula(this in PropertyData data) =>
            data.Base.MaxEncumbered + data.CalcStrength() * StrengthToEncumbered;
        
        private static float CalcMoveSpeedWithFormula(this in PropertyData data) =>
            data.Base.MoveSpeed;
        
        private static float CalcRotateSpeedWithFormula(this in PropertyData data) =>
            data.Base.MoveSpeed;
        
        private static float CalcMaxTroopsWithFormula(this in PropertyData data) =>
            data.Base.MaxTroops;
        
        
        public static float CalcPropertyWithFormula(this in PropertyData data, PropertyType propertyType)
        {
            return propertyType switch
            {
                // Advanced
                PropertyType.Strength => data.CalcStrength(),
                PropertyType.Dexterity => data.CalcDexterity(),
                PropertyType.Constitution => data.CalcConstitution(),
                PropertyType.Intellectual => data.CalcIntellectual(),
                PropertyType.Lucky => data.CalcLucky(),
                PropertyType.Charisma => data.CalcCharisma(),
                
                // Attackable
                PropertyType.MinMeleeAttack => data.CalcMinMeleeAttackWithFormula(),
                PropertyType.MaxMeleeAttack => data.CalcMaxMeleeAttackWithFormula(),
                PropertyType.MinRangeAttack => data.CalcMinRangeAttackWithFormula(),
                PropertyType.MaxRangeAttack => data.CalcMaxRangeAttackWithFormula(),
                PropertyType.AttackRange => data.CalcMaxRangeAttackWithFormula(),
                PropertyType.AttackSpeed => data.CalcAttackSpeedWithFormula(),
                PropertyType.AttackInterval => data.CalcAttackIntervalWithFormula(),
                PropertyType.Hit => data.CalcHitWithFormula(),
                PropertyType.CritRate => data.CalcCritRateWithFormula(),
                PropertyType.CritRatio => data.CalcCritRatioWithFormula(),
                PropertyType.AbilityPower => data.CalcAbilityPowerWithFormula(),
                PropertyType.Penetration => data.CalcPenetrationWithFormula(),
                PropertyType.CurAbilityPoint => data.CalcAbilityPointWithFormula(),
                PropertyType.MaxAbilityPoint => data.CalcAbilityPointWithFormula(),
                PropertyType.AbilityRecover => data.CalcAbilityRecoverWithFormula(),
                PropertyType.ExtraDamage => data.CalcExtraDamageWithFormula(),
                
                // Damageable
                PropertyType.CurHealthPoint => data.CalcMaxHealthPointWithFormula(),
                PropertyType.MaxHealthPoint => data.CalcMaxHealthPointWithFormula(),
                PropertyType.HealthRecover => data.CalcHealthRecoverWithFormula(),
                PropertyType.Armor => data.CalcArmorWithFormula(),
                PropertyType.Block => data.CalcBlockWithFormula(),
                PropertyType.Dodge => data.CalcDodgeWithFormula(),
                PropertyType.Resilience => data.CalcResilienceWithFormula(),
                PropertyType.Resistance => data.CalcResistanceWithFormula(),
                
                // Movable
                PropertyType.CurEncumbered => data.CalcMaxEncumberedWithFormula(),
                PropertyType.MaxEncumbered => data.CalcMaxEncumberedWithFormula(),
                PropertyType.MoveSpeed => data.CalcMoveSpeedWithFormula(),
                PropertyType.RotateSpeed => data.CalcRotateSpeedWithFormula(),
                PropertyType.MaxTroops => data.CalcMaxTroopsWithFormula(),

                PropertyType.Default => throw new ArgumentOutOfRangeException(nameof(propertyType), propertyType, null),
                _ => throw new ArgumentOutOfRangeException(nameof(propertyType), propertyType, null)
            };
        }

        #endregion

        #region Modify Property

        public static void ModifyProperty(PropertyData* data, PropertyModifier* modifier)
        {
            switch (modifier->Property.Type)
            {
                case PropertyType.Strength:
                    if (PropertyModifyType.Base == modifier->ModifyType)
                        data->Base.Strength += modifier->Property.Value;
                    else
                        data->Advanced.Strength += modifier->Property.Value;
                    break;
                case PropertyType.Dexterity:
                    if (PropertyModifyType.Base == modifier->ModifyType)
                        data->Base.Dexterity += modifier->Property.Value;
                    else
                        data->Advanced.Dexterity += modifier->Property.Value;
                    break;
                case PropertyType.Constitution:
                    if (PropertyModifyType.Base == modifier->ModifyType)
                        data->Base.Constitution += modifier->Property.Value;
                    else
                        data->Advanced.Constitution += modifier->Property.Value;
                    break;
                case PropertyType.Intellectual:
                    if (PropertyModifyType.Base == modifier->ModifyType)
                        data->Base.Intellectual += modifier->Property.Value;
                    else
                        data->Advanced.Intellectual += modifier->Property.Value;
                    break;
                case PropertyType.Lucky:
                    if (PropertyModifyType.Base == modifier->ModifyType)
                        data->Base.Lucky += modifier->Property.Value;
                    else
                        data->Advanced.Lucky += modifier->Property.Value;
                    break;
                case PropertyType.Charisma:
                    if (PropertyModifyType.Base == modifier->ModifyType)
                        data->Base.Charisma += modifier->Property.Value;
                    else
                        data->Advanced.Charisma += modifier->Property.Value;
                    break;
                case PropertyType.AttackSpeed:
                    data->Base.AttackSpeed += modifier->Property.Value;
                    break;
                case PropertyType.AttackInterval:
                    data->Base.AttackInterval += modifier->Property.Value;
                    break;
                case PropertyType.MaxEncumbered:
                    data->Base.MaxEncumbered += modifier->Property.Value;
                    break;
                case PropertyType.MoveSpeed:
                    data->Base.MoveSpeed += modifier->Property.Value;
                    break; 
                case PropertyType.MaxTroops:
                    data->Base.MaxTroops += modifier->Property.Value;
                    break;
            }
        }

        public static void ModifyProperty(Attackable* attackable, PropertyModifier* ptrModifier)
        {
            switch (ptrModifier->Property.Type)
            {
                case PropertyType.MinMeleeAttack:
                    attackable->Detail.MinMeleeAttack += ptrModifier->Property.Value;
                    break;
                case PropertyType.MaxMeleeAttack:
                    attackable->Detail.MaxMeleeAttack += ptrModifier->Property.Value;
                    break;
                case PropertyType.MinRangeAttack:
                    attackable->Detail.MinRangeAttack += ptrModifier->Property.Value;
                    break;
                case PropertyType.MaxRangeAttack:
                    attackable->Detail.MaxRangeAttack += ptrModifier->Property.Value;
                    break;
                case PropertyType.AttackRange:
                    attackable->Detail.AttackRange += ptrModifier->Property.Value;
                    break;
                case PropertyType.Hit:
                    attackable->Detail.Hit += ptrModifier->Property.Value;
                    break;
                case PropertyType.CritRate:
                    attackable->Detail.CritRate += ptrModifier->Property.Value;
                    break;
                case PropertyType.CritRatio:
                    attackable->Detail.CritRatio += ptrModifier->Property.Value;
                    break;
                case PropertyType.ExtraDamage:
                    attackable->Detail.ExtraDamage += ptrModifier->Property.Value;
                    break;
                case PropertyType.AbilityPower:
                    attackable->Detail.AbilityPower += ptrModifier->Property.Value;
                    break;
                case PropertyType.CurAbilityPoint:
                    attackable->Detail.CurAbilityPoint += ptrModifier->Property.Value;
                    break;
                case PropertyType.MaxAbilityPoint:
                    attackable->Detail.MaxAbilityPoint += ptrModifier->Property.Value;
                    break;
                case PropertyType.AbilityRecover:
                    attackable->Detail.AbilityRecover += ptrModifier->Property.Value;
                    break;
                case PropertyType.Penetration:
                    attackable->Detail.Penetration += ptrModifier->Property.Value;
                    break;
            }
        }

        public static void ModifyProperty(Damageable* damageable, PropertyModifier* ptrModifier)
        {
            switch (ptrModifier->Property.Type)
            {
                case PropertyType.CurHealthPoint:
                    damageable->Detail.CurHealthPoint += ptrModifier->Property.Value;
                    break;
                case PropertyType.MaxHealthPoint:
                    damageable->Detail.MaxHealthPoint += ptrModifier->Property.Value;
                    break;
                case PropertyType.HealthRecover:
                    damageable->Detail.HealthRecover += ptrModifier->Property.Value;
                    break;
                case PropertyType.Armor:
                    damageable->Detail.Armor += ptrModifier->Property.Value;
                    break;
                case PropertyType.Block:
                    damageable->Detail.Block += ptrModifier->Property.Value;
                    break;
                case PropertyType.Dodge:
                    damageable->Detail.Dodge += ptrModifier->Property.Value;
                    break;
                case PropertyType.Resistance:
                    damageable->Detail.Resistance += ptrModifier->Property.Value;
                    break;
            }
        }
        
        public static void ModifyProperty(Movable* movable, PropertyModifier* ptrModifier)
        {
            switch (ptrModifier->Property.Type)
            {
                case PropertyType.CurEncumbered:
                    movable->Detail.CurEncumbered += ptrModifier->Property.Value;
                    break;
                case PropertyType.MaxEncumbered:
                    movable->Detail.MaxEncumbered += ptrModifier->Property.Value;
                    break;
                case PropertyType.MoveSpeed:
                    movable->Detail.MoveSpeed += ptrModifier->Property.Value;
                    break;
                case PropertyType.RotateSpeed:
                    movable->Detail.RotateSpeed += ptrModifier->Property.Value;
                    break;
            }
        }

        #endregion

        #region Refresh Property

        public static void RefreshAttackable(ref Attackable attackable, in PropertyData data)
        {
            attackable.Detail.MinMeleeAttack = data.CalcPropertyWithFormula(PropertyType.MinMeleeAttack);
            attackable.Detail.MaxMeleeAttack = data.CalcPropertyWithFormula(PropertyType.MaxMeleeAttack);
            attackable.Detail.MinRangeAttack = data.CalcPropertyWithFormula(PropertyType.MinRangeAttack);
            attackable.Detail.MaxRangeAttack = data.CalcPropertyWithFormula(PropertyType.MaxRangeAttack);
            attackable.Detail.AttackRange = data.CalcPropertyWithFormula(PropertyType.AttackRange);
            attackable.Detail.AttackInterval = data.CalcPropertyWithFormula(PropertyType.AttackInterval);
            attackable.Detail.Hit = data.CalcPropertyWithFormula(PropertyType.Hit);
            attackable.Detail.CritRate = data.CalcPropertyWithFormula(PropertyType.CritRate);
            attackable.Detail.CritRatio = data.CalcPropertyWithFormula(PropertyType.CritRatio);
            attackable.Detail.AbilityPower = data.CalcPropertyWithFormula(PropertyType.AbilityPower);
            attackable.Detail.Penetration = data.CalcPropertyWithFormula(PropertyType.Penetration);
            attackable.Detail.MaxAbilityPoint = data.CalcPropertyWithFormula(PropertyType.MaxAbilityPoint);
            attackable.Detail.AbilityRecover = data.CalcPropertyWithFormula(PropertyType.AbilityRecover);
            attackable.Detail.ExtraDamage = data.CalcPropertyWithFormula(PropertyType.ExtraDamage); 
        }

        public static void RefreshDamageable(ref Damageable damageable, in PropertyData data)
        {
            damageable.Detail.MaxHealthPoint = data.CalcPropertyWithFormula(PropertyType.MaxHealthPoint);
            damageable.Detail.HealthRecover = data.CalcPropertyWithFormula(PropertyType.HealthRecover);
            damageable.Detail.Armor = data.CalcPropertyWithFormula(PropertyType.Armor);
            damageable.Detail.Block = data.CalcPropertyWithFormula(PropertyType.Block);
            damageable.Detail.Dodge = data.CalcPropertyWithFormula(PropertyType.Dodge);
            damageable.Detail.Resilience = data.CalcPropertyWithFormula(PropertyType.Resilience);
            damageable.Detail.Resistance = data.CalcPropertyWithFormula(PropertyType.Resistance);
        }
        
        public static void RefreshMovable(ref Movable movable, in PropertyData data)
        {
            movable.Detail.MaxEncumbered = data.CalcPropertyWithFormula(PropertyType.MaxEncumbered);
            movable.Detail.MoveSpeed = data.CalcPropertyWithFormula(PropertyType.MoveSpeed);
            movable.Detail.RotateSpeed = data.CalcPropertyWithFormula(PropertyType.RotateSpeed);
        }
        
        #endregion
    }
}